/*
 *
 * *******************************************************************
 *   @项目名称: BHex Android
 *   @文件名称: HttpUtils.java
 *   @Date: 11/29/18 3:21 PM
 *   @Author: chenjun
 *   @Copyright（C）: 2018 BlueHelix Inc.   All rights reserved.
 *   注意：本内容仅限于内部传阅，禁止外泄以及用于其他的商业目的.
 *  *******************************************************************
 *
 */

package io.bhex.baselib.network;

import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.alibaba.fastjson.JSON;
import com.datatheorem.android.trustkit.TrustKit;
import com.facebook.stetho.okhttp3.StethoInterceptor;

import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import io.bhex.baselib.core.CApplication;
import io.bhex.baselib.network.Utils.CookieUtils;
import io.bhex.baselib.network.cookie.persistentcookiejar.ClearableCookieJar;
import io.bhex.baselib.network.cookie.persistentcookiejar.PersistentCookieJar;
import io.bhex.baselib.network.cookie.persistentcookiejar.cache.SetCookieCache;
import io.bhex.baselib.network.cookie.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import io.bhex.baselib.network.exception.NetException;
import io.bhex.baselib.network.interceptor.Interceptor;
import io.bhex.baselib.network.interceptor.InterceptorWrapper;
import io.bhex.baselib.network.interceptor.ResponseInterceptor;
import io.bhex.baselib.network.interceptor.ResponseInterceptors;
import io.bhex.baselib.network.params.GetParams;
import io.bhex.baselib.network.params.IParams;
import io.bhex.baselib.network.params.PostParams;
import io.bhex.baselib.network.params.UploadParam;
import io.bhex.baselib.network.request.RequestFactory;
import io.bhex.baselib.network.response.BaseResponse;
import io.bhex.baselib.network.response.ResponseListener;
import io.bhex.baselib.utils.DebugLog;
import io.bhex.baselib.utils.NetworkUtils;
import io.bhex.sdk.BhexSdk;
import io.bhex.sdk.config.domain.BackupDomainManager;
import io.bhex.sdk.utils.bean.DomainsBean;
import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dns;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * 处理网络请求的全局类，用于隔离网络请求库和应用
 * ================================================
 */
public class HttpUtils {

    private static final int DEFAULT_CONNECT_TIME_OUT = 10;
    private static final int DEFAULT_TIME_OUT = 10;
    private static HttpUtils _instance;
    public static final MediaType JSONTYPE = MediaType.parse("application/json; charset=utf-8");

    public static HttpUtils getInstance() {
        if (_instance == null)
            _instance = new HttpUtils();
        return _instance;
    }

    private OkHttpClient DEFAULT_CLIENT;

    private int MAX_PRE_HOST_COUNT = 10;

    private HashMap<String, OkHttpClient> SSL_PIN_CLIENT_MAP = null;

    public OkHttpClient getHttpClient() {
        return DEFAULT_CLIENT;
    }

    /**
     * 用于主线程内处理返回结果
     */
    private Handler uiHandler;

    /**
     * response拦截器
     */
    private ResponseInterceptors responseInterceptors;

    private InterceptorWrapper interceptorWrapper;

    //    //cookie存储
//    private ConcurrentHashMap<String, List<Cookie>> cookieStore = new ConcurrentHashMap<>();
//    CookieJar cookieJar = new CookieJar()
//    {//这里可以做cookie传递，保存等操作
//        @Override
//        public void saveFromResponse(HttpUrl url, List<Cookie> cookies)
//        {//可以做保存cookies操作
//            cookieStore.put(url.host(), cookies);
//        }
//
//        @Override
//        public List<Cookie> loadForRequest(HttpUrl url)
//        {//加载新的cookies
//            List<Cookie> cookies = cookieStore.get(url.host());
//            return cookies != null ? cookies : new ArrayList<Cookie>();
//        }
//    };

    ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(CApplication.getInstance()));

    private HttpUtils() {
        //设置缓存目录, 优先存储在sd卡

        File cacheFile;
        try {
            if (Environment.getExternalStorageDirectory().exists()){
                cacheFile = new File(CApplication.getInstance().getExternalCacheDir(), "network");
            } else{
                cacheFile = new File(CApplication.getInstance().getCacheDir(), "network");
            }
        } catch (Exception e) {
            cacheFile = new File(CApplication.getInstance().getCacheDir(), "network");
        }
        if(cacheFile!=null && !cacheFile.exists()){
            cacheFile.mkdir();
        }

        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
        //DebugLog.d("HttpUtils","cacheFile==1:"+cacheFile.getAbsolutePath());
        interceptorWrapper = new InterceptorWrapper();
        OkHttpClient.Builder clientBuild = new OkHttpClient.Builder();
        //设置一下整体的超时
        clientBuild.connectTimeout(DEFAULT_CONNECT_TIME_OUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)//允许失败重试
                //.connectTimeout(2, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
                .cookieJar(cookieJar)
                .cache(cache)
                //.addInterceptor(new CacheControExtlInterceptor(cacheFile.getAbsolutePath()))
                .addInterceptor(interceptorWrapper);


        if (DebugLog.isDebuggable()) {
            clientBuild.eventListenerFactory(HttpEventListener.FACTORY);
            clientBuild.addNetworkInterceptor(new StethoInterceptor());
        }

        //if (DebugLog.isDebuggable()) {
        clientBuild.eventListenerFactory(HttpEventListener.FACTORY);
        clientBuild.sslSocketFactory(createSSLSocketFactory());
        clientBuild.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        //clientBuild.proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("127.23.0.67", 8888)))
        //}
        //else
        //clientBuild.proxy(Proxy.NO_PROXY);
        DEFAULT_CLIENT = clientBuild.build();
        DEFAULT_CLIENT.dispatcher().setMaxRequests(MAX_PRE_HOST_COUNT);

        //用于主线程消息发送
        uiHandler = new Handler(Looper.getMainLooper());

        responseInterceptors = new ResponseInterceptors();
    }

    public void initSslPin(List<DomainsBean.domain> domains) {
        if (SSL_PIN_CLIENT_MAP == null) {
            SSL_PIN_CLIENT_MAP = new HashMap<>();
        }

        for (DomainsBean.domain domain :
                domains) {
            String host;
            if (domain.domain.contains(":")) {
                host = domain.domain.split(":")[0];
            } else {
                host = domain.domain;
            }

            if (SSL_PIN_CLIENT_MAP.get(host) != null) {
                continue;
            }

            File cacheFile;
            String cacheFileName = host + ".cache";
            try {
                if (Environment.getExternalStorageDirectory().exists())
                    cacheFile = new File(CApplication.getInstance().getExternalCacheDir(), cacheFileName);
                else
                    cacheFile = new File(CApplication.getInstance().getCacheDir(), cacheFileName);
            } catch (Exception e) {
                cacheFile = new File(CApplication.getInstance().getCacheDir(), cacheFileName);
            }

            DebugLog.d("HttpUtils","cacheFile==2:"+cacheFile.getAbsolutePath());

            Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);

            OkHttpClient.Builder clientBuild = new OkHttpClient.Builder();
            //设置一下整体的超时
            clientBuild.connectTimeout(DEFAULT_CONNECT_TIME_OUT, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true)//允许失败重试
                    //.connectTimeout(2, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
                    .cookieJar(cookieJar)
                    .cache(cache)
                    //.addInterceptor(new CacheControExtlInterceptor(cacheFile.getAbsolutePath()))
                    .addInterceptor(interceptorWrapper);

            if (DebugLog.isDebuggable()) {
                clientBuild.eventListenerFactory(HttpEventListener.FACTORY);
                clientBuild.addNetworkInterceptor(new StethoInterceptor());
            }

            //if (DebugLog.isDebuggable()) {
            clientBuild.eventListenerFactory(HttpEventListener.FACTORY);

            clientBuild.sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(host),
                    TrustKit.getInstance().getTrustManager(host));

            clientBuild.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
            OkHttpClient okHttpClient = clientBuild.build();
            okHttpClient.dispatcher().setMaxRequests(MAX_PRE_HOST_COUNT);
            //clientBuild.proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("127.23.0.67", 8888)))
            //}
            //else
            //clientBuild.proxy(Proxy.NO_PROXY);
            SSL_PIN_CLIENT_MAP.put(host, okHttpClient);
        }

    }

    @Deprecated
    public void addInterceptor(ResponseInterceptor interceptor) {
        responseInterceptors.add(interceptor);
    }

    @Deprecated
    public boolean removeInterceptor(ResponseInterceptor interceptor) {
        return responseInterceptors.remove(interceptor);
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptorWrapper.addInterceptor(interceptor);
    }

    public void removeInterceptor(Interceptor interceptor) {
        interceptorWrapper.removeInterceptor(interceptor);
    }

    public void cancelByTag(Object tag) {
        cancelByTag(DEFAULT_CLIENT, tag);
    }

    public void cancelByTag(OkHttpClient client, Object tag) {
        if (tag == null)
            return;

        List<Call> calls = new ArrayList<>(client.dispatcher().runningCalls());
        calls.addAll(client.dispatcher().queuedCalls());

        try {
            for (Call call : calls) {
                if (call.request().tag().equals(tag)) {
                    call.cancel();
                    break;
                }
            }
        } catch (Exception e) {
            DebugLog.e(e);
        }
    }

    private String GetBaseDomain(String hostname) {
        String[] splitHostname = hostname.split("\\.");
        int length = splitHostname.length;

        if (length >= 3) {
            return splitHostname[length - 2] + "." + splitHostname[length - 1];
        } else {
            return hostname;
        }
    }

    /**
     * 请求http
     */
    public <T> void request(final IParams params, Class<T> responseClazz,
                            final ResponseListener<T> listener) {
        if (TextUtils.isEmpty(params.getUrl())) {
            return;
        }

        if (SSL_PIN_CLIENT_MAP == null) {
            request(DEFAULT_CLIENT, params, responseClazz, listener);
        } else {
            String host = params.getUrlObject().getHost();
            String baseDomain = this.GetBaseDomain(host);
            OkHttpClient client = SSL_PIN_CLIENT_MAP.get(baseDomain);

            if (client == null) {
                request(DEFAULT_CLIENT, params, responseClazz, listener);
            } else {
                request(client, params, responseClazz, listener);
            }
        }
    }

    public <T> void request(final IParams params, Class<T> responseClazz,
                            final ResponseListener<T> listener, String body) {
        request(DEFAULT_CLIENT, params, responseClazz, listener, body);
    }

    public <T> void request(final OkHttpClient client, final IParams params,
                            final Class<T> responseClazz,
                            final ResponseListener<T> listener, String body) {
        DebugLog.e("http request:", "url:" + params.getUrl());
        //检查请求是否已经队列中了
        //TODO 开启一个设置，是否允许重复请求
//        if (checkExist(client, params))
//            return;
        if (params != null)
            params.setRequestTime(System.currentTimeMillis());
        if (listener != null) {//请求前的回调
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onBefore();
                }
            });
        }

        Callback callback = new Callback() {
            @Override
            public void onFailure(final Call call, final IOException e) {
//                if (e instanceof UnknownHostException || e instanceof SocketTimeoutException || e instanceof ConnectException) {
                    String host = call.request().url().host();
                    DebugLog.e("DOMAIN SWITCH", "DOMAIN SWITCH" + host);
                    if (!TextUtils.isEmpty(host)) {
                        if (BackupDomainManager.getInstance().isOwnCurrentDomains(host)) {
                            BackupDomainManager.getInstance().countHostFailed(host);
                            if (BackupDomainManager.getInstance().getHostFailedCount(host)==3) {
                                BackupDomainManager.getInstance().switchDomain();
                            }
                        }
                    }
//                }

                if (listener == null)
                    return;

                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            listener.onFinish();
                            listener.onError(e);
                            if (params != null)
                                BhexSdk.ResponseErrorNotity(params.getUrl(), "");
                            listener.onAfter();
                        } catch (Exception e) {

                        }
                    }
                });
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                String host = call.request().url().host();
                BackupDomainManager.getInstance().clearCountHostFailed(host);
                if (listener == null)
                    return;
                int code = response.code();
                Headers headers = response.headers();
                final String error_code = response.header("Error_code");
//                DebugLog.e("http response:", "response code:" + code + " error_code:" + error_code);
                if (!isSuccess(code)) { //从code中就可以判断到的失败
                    uiHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            listener.onFinish();
//                            if (!TextUtils.isEmpty(error_code)) {
//                                listener.onError(new NetException(response.code()+"", error_code));
//                            }else{
                            listener.onError(new NetException(response.code() + "", ""));
//                            }
                            if (response != null && params != null) {
                                BhexSdk.ResponseErrorNotity(params.getUrl(), String.valueOf(response.code()));

                                UploadParam uploadParam = new UploadParam();
                                uploadParam.request_url = params.getUrl();
                                String host = response.request().url().host();
                                if (!TextUtils.isEmpty(host)) {
                                    uploadParam.remote_address = NetworkUtils.getHostAddressByDomain(host);
                                }
                                uploadParam.timestamp = String.valueOf(params.getRequestTime());
                                uploadParam.cost = String.valueOf(System.currentTimeMillis() - params.getRequestTime());
                                uploadParam.http_code = String.valueOf(response.code());
                                uploadParam.error_code = error_code;
                                uploadParam.error_message = "";
                                uploadParam.net = BhexSdk.netType;
                                uploadParam.success = false;
                                BhexSdk.NetRequestNotity(uploadParam);
                            }

                            listener.onAfter();
                        }
                    });

                    return;
                }

                //先经过一层过滤拦截
                String responseData = responseInterceptors.intercept(response.body().string());

                DebugLog.e("http response:", "Url: " + response.request().url() + "\nresponse: " + responseData);
                final T result = listener.parserResponse(uiHandler, responseData, responseClazz);

                BaseResponse baseResult = null;
                try {
                    baseResult = JSON.parseObject(responseData, BaseResponse.class);
                } catch (final Exception e) {

                }

                final BaseResponse netRequestResult = baseResult;
                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        listener.onFinish();
                        listener.onSuccess(result);
                        listener.onAfter();

                        UploadParam uploadParam = new UploadParam();
                        uploadParam.request_url = params.getUrl();
                        String host = response.request().url().host();
                        if (!TextUtils.isEmpty(host)) {
                            uploadParam.remote_address = NetworkUtils.getHostAddressByDomain(host);
                        }
                        uploadParam.timestamp = String.valueOf(params.getRequestTime());
                        uploadParam.cost = String.valueOf(System.currentTimeMillis() - params.getRequestTime());
                        uploadParam.http_code = String.valueOf(response.code());
                        if (netRequestResult != null) {
                            uploadParam.error_code = netRequestResult.getCode();
                            uploadParam.error_message = netRequestResult.getMsg();
                        }
                        uploadParam.net = BhexSdk.netType;
                        uploadParam.success = response.code() != 400 || response.code() != 500;
                        BhexSdk.NetRequestNotity(uploadParam);
                    }
                });
            }
        };
        if (params instanceof PostParams) {

            sig(((PostParams) params).postParams());

        } else if (params instanceof GetParams) {
            sig(((GetParams) params).getParams());
        }


        Request request = RequestFactory.createRequest(params);
        if (!TextUtils.isEmpty(body)) {
            RequestBody jsonBody = RequestBody.create(JSONTYPE, body);
            request = request.newBuilder().post(jsonBody).build();
        }

        Call currentCall = client.newCall(request);
        currentCall.enqueue(callback);
    }

    public <T> void request(OkHttpClient client, final IParams params,
                            final Class<T> responseClazz,
                            final ResponseListener<T> listener) {
        request(client, params, responseClazz, listener, null);
    }

    /**
     * @param code 是否是成功响应 增加成功响应Code 400、500
     *             code==400（业务处理错误提示）
     *             格式：
     *             {
     *             "code": 1105,
     *             "msg": "Username or password is incorrect"
     *             }
     *             <p>
     *             code==500（Server Internal server error）
     *             格式：
     *             {
     *             "code": 110,
     *             "msg": "Internal server error"
     *             }
     * @return
     */
    private boolean isSuccess(int code) {
        return (code >= 200 && code < 300) || code == 400 || code == 500;
    }

    /**
     * 是否已经存在
     *
     * @param client
     * @param params
     * @return
     */
    private boolean checkExist(OkHttpClient client, IParams params) {
        List<Call> calls = new ArrayList<>(client.dispatcher().runningCalls());
        calls.addAll(client.dispatcher().queuedCalls());
        for (Call call : calls) {
            if (call.request().url().equals(params.getUrl()) && !call.isCanceled()) {
                return true;
            }
        }

        return false;
    }


    private void sig(Map<String, Object> map) {
        if (map != null) {
            map.put("time", String.valueOf(System.currentTimeMillis()));
            if (!TextUtils.isEmpty(CookieUtils.getCToken())) {
                map.put("c_token", CookieUtils.getCToken());
            }
        }

        map.remove("sig");

        List<String> sortedParams = new ArrayList<String>();

        for (Map.Entry<String, Object> m : map.entrySet()) {
            //if(m.getValue() instanceof  String)
            sortedParams.add(m.getKey());
        }

        StringBuilder sb = new StringBuilder();
        Collections.sort(sortedParams);


        for (String paramKey : sortedParams) {
            sb.append(paramKey).append('=').append(defaultString(String.valueOf(map.get(paramKey))));
        }
        String sig = Encode.encodeMD5(sb.toString(), CApplication.getInstance());
        map.put("sig", sig);
    }

    public static String defaultString(String str) {
        return str == null ? "" : str;
    }

    public void setHttpDns(Dns dns) {
        //if(DEFAULT_CLIENT != null)
        //   DEFAULT_CLIENT = DEFAULT_CLIENT.newBuilder().dns(dns).build();
    }

    private static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory ssfFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom());

            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }

        return ssfFactory;
    }
}

class TrustAllCerts implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}
